<template>
  <div class="preview">
    <studio-header />
    <div class="content-box">
      <div class="container" :style="containerStyle">
        <grid-layout
          ref="gridLayout"
          :layout="gridList"
          :col-num="dataStore.gridColNum"
          :row-height="dataStore.gridRowHeight"
          :is-draggable="false"
          :is-resizable="false"
          :is-mirrored="false"
          :vertical-compact="false"
          :margin="dataStore.girdMargin"
          :use-css-transforms="true"
          :style="{
            background: dataStore.layoutData.background,
          }"
        >
          <template v-for="(item, index) in gridList">
            <grid-item
              v-if="item.chartType === 'filter'"
              :id="`preview-grid-item-${item.i}`"
              :key="`preview_${item.i}`"
              class="grid-item filter"
              :scale="dataStore.scaleValue"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
              :min-w="2"
              :min-h="1"
            >
              <div
                :ref="`control-list-${item.i}`"
                class="control-box-list"
                :class="`list-${item.layout}`"
              >
                <template v-for="(controlConfig, j) in item.fieldConfig">
                  <controlVue
                    :key="j"
                    v-model="controlConfig.value"
                    :grid-item="item"
                    :field-config="controlConfig"
                    :layout="item.layout"
                    :control-type="controlConfig.controlRender"
                    :prop="{ size: 'default' }"
                    :label="controlConfig.name"
                    :options="controlConfig.options"
                    @change="itemChange(item, controlConfig)"
                  ></controlVue>
                </template>
              </div>
            </grid-item>
            <grid-item
              v-else-if="item.chartType === 'table'"
              :id="`preview-grid-item-${item.i}`"
              :ref="`grid-item-${item.i}`"
              :key="`preview-${item.i}`"
              class="grid-item"
              :class="{
                'hover-border':
                  currentFilterFormItemAsCharts.length === 0 &&
                  checkIfShowHoverStatus(item.widgetId),
                'select-border':
                  currentFilterFormItemAsCharts.length === 0 &&
                  selectedParentList.includes(item.widgetId),
                active:
                  currentFilterFormItemAsCharts.includes(item.widgetId) ||
                  filterAssociationChart.includes(item.widgetId),
              }"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
            >
              <div v-if="item.isLoading" class="loading-mask">
                <div v-if="item.isLoading" class="loading-status">
                  <a-icon class="sync-icon" type="sync" spin /><span
                    class="loading-text"
                    >加载中...</span
                  >
                </div>
                <!-- <img class="loading-status-img" src="~@/assets/chart-loading.png" alt="加载中..."> -->
              </div>
              <div
                v-if="hideWidgetList.includes(item.widgetId)"
                :class="{
                  'interact-mask': true,
                  'no-title':
                    !item.chartOptions.title || !item.chartOptions.showChart,
                }"
              ></div>
              <div
                v-if="
                  !item.isLoading &&
                  item.chartOptions &&
                  item.chartOptions.value &&
                  item.chartOptions.value.length === 0
                "
                class="no-data"
              >
                <div class="no-data-block">
                  <img
                    class="no-data-img"
                    src="~@/assets/no-data.png"
                    alt="no-data"
                  />
                  <span class="no-data-text">{{
                    errorFilterWidgets.includes(item.i)
                      ? errorMsgMap[item.i]
                      : '暂无数据'
                  }}</span>
                </div>
              </div>
              <div
                v-if="item.chartOptions"
                class="title-box"
                :class="{
                  'no-title': item.chartOptions.title === '',
                }"
              >
                {{ item.chartOptions ? item.chartOptions.title || '' : '' }}
              </div>
              <pageTableVue
                :id="`grid-${item.i}`"
                :ref="`chart-${item.i}`"
                :is-sortable="true"
                :config="item"
                :class="{
                  'no-title': item.chartOptions.title === '',
                }"
                @pagechange="
                  (pageInfo) => {
                    onPageChange(item, index, pageInfo)
                  }
                "
                @sortchange="
                  (sortData) => {
                    sortchange(item, index, sortData)
                  }
                "
              >
              </pageTableVue>
            </grid-item>
            <grid-item
              v-else-if="item.chartType === 'configuration'"
              :id="`preview-grid-item-${item.i}`"
              :ref="`grid-item-${item.i}`"
              :key="`preview-${item.i}`"
              class="grid-item"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
            >
              <div
                v-if="item.chartOptions"
                class="title-box"
                :class="{
                  'no-title': item.chartOptions.title === '',
                }"
              >
                {{ item.chartOptions ? item.chartOptions.title || '' : '' }}
              </div>
              <operator-configuration
                :style="{
                  height:
                    item.chartOptions && item.chartOptions.title
                      ? 'calc(100% - 38px)'
                      : '100%',
                }"
                :config="item.chartOptions"
              />
            </grid-item>
            <grid-item
              v-else-if="item.chartType === 'text'"
              :id="`preview-grid-item-${item.i}`"
              :ref="`grid-item-${item.i}`"
              :key="`preview-${item.i}`"
              class="grid-item"
              :class="{
                'remove-border': !item.chartOptions.showBorder,
              }"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
            >
              <div v-if="item.isLoading" class="loading-mask">
                <div v-if="item.isLoading" class="loading-status">
                  <a-icon class="sync-icon" type="sync" spin /><span
                    class="loading-text"
                    >加载中...</span
                  >
                </div>
              </div>
              <div
                v-if="
                  !item.isLoading &&
                  item.chartOptions &&
                  item.chartOptions.value &&
                  item.chartOptions.value.length === 0
                "
                class="no-data"
              >
                <div class="no-data-block">
                  <img
                    class="no-data-img"
                    src="~@/assets/no-data.png"
                    alt="no-data"
                  />
                  <span class="no-data-text">暂无数据</span>
                </div>
              </div>
              <div
                v-if="item.chartOptions"
                class="title-box"
                :class="{
                  'remove-title': !item.chartOptions.showTitle,
                }"
              >
                {{ item.chartOptions ? item.chartOptions.title || '' : '' }}
              </div>
              <text-editor
                :id="`preview-grid-item-${item.i}`"
                class="text-editor"
                :class="{
                  'height-reset': !item.chartOptions.showTitle,
                }"
                :text-value="item.chartOptions.value"
                :read-only="true"
                @render-complete="removeLoading(index)"
              >
              </text-editor>
            </grid-item>
            <grid-item
              v-else-if="checkIsFilterForm(item)"
              :id="`filter-form-grid-item-${item.i}`"
              :ref="`grid-item-${item.i}`"
              :key="`filter-form-${item.i}`"
              class="grid-item"
              :class="{
                'active-border': currentFilterIndex === item.i,
              }"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
            >
              <div
                v-if="item.chartOptions"
                class="title-box"
                :class="{
                  'no-title': item.chartOptions.title === '',
                }"
              >
                {{ item.chartOptions ? item.chartOptions.title || '' : '' }}

                <filter-form-toolbar
                  v-if="checkFilterFormIsEnabled(item)"
                  :grid-item="item"
                  :index="index"
                  :disabled="isFilterFormTaskRunning"
                  :is-running="checkIsRunning(item)"
                  @reset="
                    () => {
                      filterFormClicked(item)
                      filterFormReset(item)
                    }
                  "
                  @start="
                    () => {
                      filterFormClicked(item)
                      filterFormExecute(true, item, index)
                    }
                  "
                  @stop="
                    () => {
                      filterFormClicked(item)
                      filterFormExecute(false, item, index)
                    }
                  "
                />
              </div>
              <!-- 筛选器控件 -->
              <div
                class="filter-form-hander"
                @click="filterFormClicked(item)"
                @mouseenter="
                  () => {
                    handleFilterFormMouseEnter(item)
                  }
                "
                @mouseleave="
                  () => {
                    handleFilterFormMouseLeave(item)
                  }
                "
              >
                <filter-form
                  v-if="dataset && dataset.length > 0 && pipelineId"
                  :ref="`filter-form-${item.i}`"
                  :config="item"
                  :dataset="dataset"
                  :pipeline-id="pipelineId"
                  :node-id-map="{}"
                  @change="
                    (dataSet) => {
                      filterChange(dataSet, item, index)
                    }
                  "
                ></filter-form>
              </div>
            </grid-item>
            <grid-item
              v-else
              :id="`preview-grid-item-${item.i}`"
              :ref="`grid-item-${item.i}`"
              :key="`preview-${item.i}`"
              class="grid-item"
              :class="{
                'hover-border':
                  currentFilterFormItemAsCharts.length === 0 &&
                  checkIfShowHoverStatus(item.widgetId),
                'select-border':
                  currentFilterFormItemAsCharts.length === 0 &&
                  selectedParentList.includes(item.widgetId),
                active:
                  currentFilterFormItemAsCharts.includes(item.widgetId) ||
                  filterAssociationChart.includes(item.widgetId),
              }"
              :x="item.x"
              :y="item.y"
              :w="item.w"
              :h="item.h"
              :i="item.i"
            >
              <div v-if="item.isLoading" class="loading-mask">
                <div v-if="item.isLoading" class="loading-status">
                  <a-icon class="sync-icon" type="sync" spin /><span
                    class="loading-text"
                    >加载中...</span
                  >
                </div>
              </div>
              <!-- 标题 -->
              <div
                v-if="
                  !item.showChart &&
                  item.chartOptions &&
                  item.chartType !== 'filter'
                "
                class="title-box"
                :class="{
                  'no-title': [undefined, null, ''].includes(
                    item.chartOptions.title
                  ),
                }"
              >
                {{ item.chartOptions ? item.chartOptions.title || '' : '' }}
              </div>
              <div
                v-if="hideWidgetList.includes(item.widgetId)"
                :class="{
                  'interact-mask': true,
                  'no-title':
                    !item.chartOptions.title || !item.chartOptions.showChart,
                }"
              ></div>
              <div
                v-if="
                  !item.isLoading &&
                  item.chartOptions &&
                  item.chartOptions.value &&
                  checkIsNoData(item)
                "
                class="no-data"
              >
                <div class="no-data-block">
                  <img
                    class="no-data-img"
                    src="~@/assets/no-data.png"
                    alt="no-data"
                  />
                  <span class="no-data-text">{{
                    errorFilterWidgets.includes(item.i)
                      ? errorMsgMap[item.i]
                      : '暂无数据'
                  }}</span>
                </div>
              </div>
              <Chart
                v-show="
                  checkIsGeoMap(item) ||
                  (item.showChart &&
                    item.chartOptions.value &&
                    !checkIsNoData(item))
                "
                :id="`preview-grid-${item.i}`"
                :config="item.chartOptions"
                :chart-type="item.chartType"
                @chartinit="
                  (instance) => {
                    onChartInstanceInit(instance, item)
                  }
                "
                @chartevent="
                  (data) => {
                    onChartEvent(data, item, index)
                  }
                "
              >
                <div
                  v-if="setting.tooltipShow"
                  slot="graph_tooltip"
                  slot-scope="{ setting, chart }"
                  class="graph-tooltip"
                  :style="{
                    left: setting.nodeLinkTooltipPosition.left + 'px',
                    top: setting.nodeLinkTooltipPosition.top + 'px',
                  }"
                >
                  <p
                    v-for="(attrInfo, i) in getGraphTooltipInfo(chart, setting)"
                    :key="i"
                  >
                    {{ attrInfo }}
                  </p>
                </div>
              </Chart>
            </grid-item>
          </template>
        </grid-layout>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import StudioHeader from '@/components/studio/StudioHeader.vue'
import VisualizationStore from '@/store/modules/visualization'
import {
  componentTypeEnum,
  ControlConfigurationInterface,
  GridItemDataInterface,
  gridItemFilterInterface,
  KeyValueStore,
} from '@/components/vueGridLayout/interfate/grid-Item-data-inteface'
import Chart from '@/components/chart/chart.vue'
import controlVue from '@/components/control/control.vue'
import GridLayout from '@/components/vueGridLayout/components/GridLayout.vue'
import GridItem from '@/components/vueGridLayout/components/GridItem.vue'
import OperatorConfiguration from '@/components/visualization/OperatorConfiguration.vue'
import { cloneDeep, isArray, isString } from 'lodash'
import { filterFormTypeName, GEOMAP_NAME, GRAPH_ARRAY } from '@/config/contant'
import { getWidgetData } from '@/api/widget'
import {
  bindChartData,
  checkFilterFormIsEnabled,
  checkIsFilterForm,
  filterGraphData,
  getFilterDataFromFilterForm,
  mergeFilterFormFilters,
} from '@/util/common'
import { checkArrayIncludes } from '@/util/util'
import pageTableVue from '@/components/pageTable/pageTable.vue'
import TextEditor from '@/components/textEditor/textEditor.vue'

import FilterFormToolbar from '@/components/filter-form/filter-form-toolbar.vue'
import FilterForm from '@/components/filter-form/index.vue'

import {
  getWidgetGraphData,
  parseGetDataResult,
  restructGetDataResult,
  parseChartOptionToFunction,
} from '@/components/common/VisualizationDashboard/util'
import { message } from 'ant-design-vue'
import { HoverFilterFormInfo } from '@/components/visualization/util'

// const otherCharts = new Set([
//   'filter',
//   'table',
//   'configuration',
//   'text',
//   'filterForm'
// ])

// import { VISUALIZATION_PATH } from '@/router/studio'

//  preview 中不支持算子控件的联动效果

@Component({
  components: {
    StudioHeader,
    Chart,
    GridLayout,
    GridItem,
    controlVue,
    pageTableVue,
    TextEditor,
    OperatorConfiguration,
    FilterFormToolbar,
    FilterForm,
  },
})
export default class PreviewView extends Vue {
  public get containerStyle() {
    return {
      width: `${VisualizationStore.layoutData.width}px`,
      background: VisualizationStore.layoutData.background,
    }
  }

  get projectId() {
    return (this.$route.params as any).id
  }

  public chartInstanceMaps: KeyValueStore = {}

  public isFilterFormTaskRunning: boolean = false

  public barChart = 'barChart'

  public gridList: Array<any> = []

  private interactRelateList: Array<any> = [] //  [{child:..., parents:...}]
  private reverstInteractList: Array<any> = [] //  [{parent:..., children:...}]
  private connectedWidgetList: Array<Number> = []
  private parentList: Array<Number> = []
  private selectedParentList: Array<Number> = []
  private hideWidgetList: Array<Number> = []

  filterFormExecuteId: string | number = ''

  mounted() {
    VisualizationStore.setSelectedChartInfo([])
    this.gridList = this.sortLayoutItemsByRowCol(
      JSON.parse(JSON.stringify(VisualizationStore.gridList))
    )
    this.$nextTick(() => {
      //  get interactRelateList
      this.parseInteractInfo()

      this.gridList.forEach((gridItem: GridItemDataInterface) => {
        // if (
        //   (gridItem.chartType.includes('barChart') ||
        //     gridItem.chartType.includes('heatmapMatrix')) &&
        //   gridItem.chartOptions.legendBasicColor
        // ) {
        //   gridItem.chartOptions.legendBasicColor =
        //     gridItem.chartOptions.legendBasicColor
        // }
        parseChartOptionToFunction(gridItem.chartOptions)
        if (gridItem.chartType === 'table') {
          if (gridItem.chartOptions.dataId) {
            this.$set(gridItem.chartOptions, 'trigger', Math.random())
          }
        }
        if (gridItem.chartType === 'text') {
          this.$set(gridItem, 'isLoading', true)
        }
      })
      // this.parseInteractInfo()
    })
  }

  //  判断当前chart 是否显示hover status
  private checkIfShowHoverStatus(id: Number) {
    if (this.selectedParentList.length > 0) {
      //  存在parent chart 被点击的情况
      const result: any = []
      this.selectedParentList.forEach((i: Number) => {
        const target = this.reverstInteractList.find((j: any) => {
          return j.parent === i
        })
        result.push(...target?.children)
      })

      return result.includes(id)
    }

    return false
  }

  private parseInteractInfo() {
    // forEach this.gridList item - interactionJson - relateWidgets
    const result: any = []
    const reverseResult: any = []
    this.gridList.forEach((item: any) => {
      if (
        item.interactionJson?.relateWidgets &&
        item.interactionJson?.relateWidgets.length > 0
      ) {
        const list = item.interactionJson?.relateWidgets
        list.forEach((widget: any) => {
          if (widget.status === 'connected') {
            //  for reverse direction
            const indexR = reverseResult.findIndex(
              (i: any) => i.parent === item.widgetId
            )
            if (indexR >= 0) {
              /** 防止children是undfined */
              reverseResult[indexR].children =
                reverseResult[indexR].children || []
              reverseResult[indexR].children.push(widget.widgetId)
            } else {
              reverseResult.push({
                parent: item.widgetId,
                children: [widget.widgetId], // 后续需要push
              })
            }
            //  for right direction
            const index = result.findIndex(
              (i: any) => i.child === widget.widgetId
            )
            if (index >= 0) {
              result[index].parents.push(item.widgetId)
            } else {
              result.push({
                child: widget.widgetId,
                parents: [item.widgetId], // 后续需要push
              })
            }
          }
        })
      }
    })
    this.interactRelateList = result
    this.reverstInteractList = reverseResult

    //  get connected widget list; [1,2,3,4]
    this.interactRelateList.forEach((item: any) => {
      this.connectedWidgetList.push(item.child)
      this.parentList.push(...item.parents)
    })
    this.hideWidgetList = this.connectedWidgetList.slice()
    this.parentList = [...new Set([...this.parentList])] //  remove duplicated param
  }

  public removeLoading(index: number) {
    this.$set(this.gridList[index], 'isLoading', false)
  }

  sortchange(item: GridItemDataInterface, index: number, sortData: any) {
    if (item.chartType === 'table' && item.chartOptions) {
      this.$set(item.chartOptions, 'sortCol', [
        //  need change
        { name: sortData.key, sort: sortData.sort },
      ])
      this.getTableData(item, [])
    }
  }

  /**
   * 由于地图组件特殊,value 不是数组
   */
  checkIsNoData(gridItem: gridItemFilterInterface) {
    if (this.checkIsGeoMap(gridItem)) {
      return false
    }
    if (this.checkIsGraph(gridItem)) {
      return false
    }
    if (isArray(gridItem.chartOptions.value)) {
      return gridItem.chartOptions.value.length === 0
    }
    return false
  }

  sortLayoutItemsByRowCol(layout: Array<any>): Array<any> {
    return layout.sort(function (a, b) {
      if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
        return 1
      }
      return -1
    })
  }

  public get dataStore() {
    return VisualizationStore
  }

  public created() {
    VisualizationStore.changeVisualizationMode('preview')
  }

  getTableData(
    gridItem: GridItemDataInterface,
    filterSend: Array<any>,
    withFilterSend?: boolean
  ) {
    const withFilter =
      this.currentFilterFormItemAsCharts.includes(gridItem.widgetId) ||
      withFilterSend
    if (gridItem && gridItem.chartOptions && gridItem.chartType === 'table') {
      const filterFormJson = withFilter ? gridItem.filterFormJson || [] : []
      const filters = gridItem.chartOptions.filters || []
      const dataSend: KeyValueStore = {
        data: {
          id: gridItem.chartOptions.dataId,
          type: gridItem.chartOptions.dataType,
          tableJson: {
            curPage: withFilterSend ? 1 : gridItem.chartOptions.curPage,
            name: '_record_id_',
            pageSize: gridItem.chartOptions.pageSize,
            sortCol: gridItem.chartOptions.sortCol,
            filters: withFilter
              ? mergeFilterFormFilters(filters, filterFormJson)
              : filters,
          },
        },
      }
      this.$set(gridItem, 'isLoading', true)
      getWidgetData(dataSend)
        .then((response: any) => {
          if (response.data.code === 1106) {
            this.errorFilterWidgets.push(gridItem.i)
            this.errorMsgMap[gridItem.i] = response.data.message
          }
          if ([100, 1106].includes(response.data.code)) {
            const gridItemClone = cloneDeep(gridItem)
            const { chartOptions } = gridItemClone
            const index = this.getGridIndex(gridItem.i as string)
            this.$set(this.gridList, index, {
              ...gridItemClone,
              isLoading: false,
              chartOptions: {
                ...chartOptions,
                curPage: response.data.result?.page?.curPage || 0,
                totalElements: response.data.result?.page?.totalElements || 0,
                pageSize: response.data.result?.page?.pageSize || 50,
                value: response.data.result
                  ? parseGetDataResult(response.data.result)
                  : [],
              },
            })
            //  remove target widget from hideList
            const targetIndex = this.hideWidgetList.indexOf(gridItem.widgetId)
            if (targetIndex >= 0) {
              this.hideWidgetList.splice(targetIndex, 1)
            }
          }
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
          this.$set(gridItem, 'isLoading', false)
        })
        .catch(() => {
          this.$set(gridItem, 'isLoading', false)
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
        })
    } else {
      console.error('call error: getTableData must be used by table')
    }
  }

  onPageChange(item: KeyValueStore, index: number, pageInfo: KeyValueStore) {
    if (item.chartType === 'table' && item.chartOptions) {
      const filters: KeyValueStore | null = this.getFilterFormData(
        item.chartOptions.dataId,
        item.i as string
      )
      const filterSend = [] as Array<any>
      if (filters) {
        Object.keys(filters).forEach((fieldName: string) => {
          const controlRender = filters[fieldName].compType
          const value = filters[fieldName].value as any
          if (
            !['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) ||
            (['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
              value[0] &&
              value[1])
          ) {
            const filterItem = this.getFilterItem(
              fieldName,
              value,
              controlRender,
              filters[fieldName].columnType
            )
            if (filterItem) {
              filterSend.push(filterItem)
            }
          }
        })
      }

      this.$set(item.chartOptions, 'pageSize', pageInfo.pageSize || 50)
      this.$set(item.chartOptions, 'curPage', pageInfo.pageNumber || 1)
      this.getTableData(item as GridItemDataInterface, filterSend)
    }
  }

  itemChange(
    gridItemFilter: gridItemFilterInterface,
    fieldInfo: KeyValueStore
  ) {
    const charts = (fieldInfo.associationChart as Array<string>) || []
    if (charts && charts.length > 0) {
      this.gridList.forEach((gridItem, index) => {
        if (charts.includes(gridItem.i as string)) {
          if (gridItem.chartType === 'table') {
            // 参数改变默认请求第一页
            this.$set(gridItem.chartOptions, 'curPage', 1)
          }
          this.getWidgetData(gridItem.chartOptions, gridItem, index)
        }
      })
    }
  }

  /**
   * 获取筛选参数
   */
  getFilterFormData(dataId: string, componentId: string): KeyValueStore | null {
    const gridFilters = this.gridList.filter((gridItem) => {
      return gridItem.chartType === 'filter' && gridItem.dataId === dataId
    })
    if (gridFilters.length > 0) {
      const result: KeyValueStore = {}
      gridFilters.forEach((item) => {
        item.fieldConfig.forEach((fieldInfo: ControlConfigurationInterface) => {
          if (fieldInfo.associationChart.includes(componentId)) {
            result[fieldInfo.column.value] = {
              compType: fieldInfo.controlRender,
              columnType: fieldInfo.columnType,
              value: fieldInfo.value,
            }
          }
        })
      })
      return result
    }
    return null
  }

  checkIsGeoMap(item: KeyValueStore) {
    return (
      item.chartType &&
      isArray(item.chartType) &&
      item.chartType[1] === GEOMAP_NAME
    )
  }

  checkIsGraph(item: KeyValueStore) {
    return GRAPH_ARRAY.includes(
      Array.isArray(item.chartType) ? item.chartType[1] : item.chartType
    )
  }

  getFilterItem(
    fieldName: string,
    value: any,
    controlType: string,
    columnType?: string
  ): KeyValueStore | null {
    let result: any = null
    switch (controlType) {
      case componentTypeEnum.radio:
      case componentTypeEnum.select:
      case componentTypeEnum.remoteSelect:
        if (value && isString(value)) {
          result = {
            col: fieldName,
            filterType: '=',
            values: [],
            checkedList: [value],
          }
        }
        break

      case componentTypeEnum.checkbox:
        if (value && isArray(value) && value.length > 0) {
          result = {
            col: fieldName,
            type: columnType,
            filterType: 'in',
            values: [],
            checkedList: value,
          }
        }
        break

      case componentTypeEnum.rangeDay:
        if (isArray(value) && value.length === 2) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp',
            values: [value[0], value[1]],
            format: 'yyyy-MM-dd',
            checkedList: [value[0], value[1]],
          }
        }
        break
      case componentTypeEnum.rangeMonth:
        if (isArray(value) && value.length === 2) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp',
            values: [value[0], value[1]],
            format: 'yyyy-MM',
            checkedList: [value[0], value[1]],
          }
        }

        break
      case componentTypeEnum.rangeYear:
        if (isArray(value) && value.length === 2) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp',
            values: [value[0], value[1]],
            format: 'yyyy',
            checkedList: [value[0], value[1]],
          }
        }
        break
      default:
        break
    }

    return result
  }

  //  getData之后，绑定最新的data for gridItem
  gridItemSyncBindData(parameterSet: any) {
    const { data, gridItem, index } = parameterSet
    const gridItemSet: GridItemDataInterface | null = bindChartData(
      gridItem,
      data
    )
    if (gridItemSet) {
      if (gridItemSet && gridItemSet.chartOptions) {
        const dom = (document as any).body.querySelector(
          `#preview-grid-item-${gridItemSet.i}`
        )
        if (dom) {
          gridItemSet.chartOptions.size = [dom.offsetWidth, dom.offsetHeight]
        }
      }
      setTimeout(() => {
        this.$set(gridItemSet, 'isLoading', false)
      }, 100)
      Vue.set(this.gridList, index, gridItemSet)
    }
  }

  //  remove selectedParentList;  remove selectedChartInfo
  private removeSelectedConfig(widgetId: Number) {
    const index = this.selecetedChartInfo.findIndex((i: any) => {
      return i.widgetId === widgetId
    })
    if (index >= 0) {
      //   VisualizationStore.removeSelectedChartInfo(index)
      VisualizationStore.setSelectedChartInfo(
        this.selecetedChartInfo.slice(0, index)
      )
      // 【直接根据数组位置，移除后面的所有】

      this.selectedParentList = this.selectedParentList.slice(0, index)
    }
  }

  /** 用于存储code为1106的response的widget */
  public errorFilterWidgets = [] as Array<any>
  /** 错误消息 */
  public errorMsgMap = {} as any

  getWidgetData(
    dataSetting: KeyValueStore,
    gridItem: any,
    index: number,
    withFilter: boolean = false
  ) {
    if (checkIsFilterForm(gridItem)) {
      return
    }
    const filters: KeyValueStore | null = this.getFilterFormData(
      dataSetting.dataId,
      gridItem.i as string
    )
    const filterSend = [] as Array<any>
    if (filters) {
      Object.keys(filters).forEach((fieldName: string) => {
        const controlRender = filters[fieldName].compType
        const value = filters[fieldName].value as any
        if (
          !['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) ||
          (['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
            value[0] &&
            value[1])
        ) {
          const filterItem = this.getFilterItem(
            fieldName,
            value,
            controlRender,
            filters[fieldName].columnType
          )
          if (filterItem) {
            filterSend.push(filterItem)
          }
        }
      })
    }
    const filterFormJson = gridItem.filterFormJson || []

    if (gridItem.chartType === 'table') {
      this.getTableData(gridItem, filterSend, withFilter)
      return
    }
    if (this.checkIsGraph(gridItem)) {
      this.getGraphData(gridItem, withFilter)
      return
    }
    const query = {
      data: {
        id: gridItem.chartOptions.dataId,
        type: gridItem.chartOptions.dataType,
        /** 将原先的参数拷贝一下，防止交互候对数据的污染 */
        widgetJson: cloneDeep(gridItem.dataQuery.data.widgetJson ?? {}),
      },
    }

    /** 筛选数据 */
    if (withFilter && query.data?.widgetJson?.config) {
      query.data.widgetJson.config.filters = mergeFilterFormFilters(
        query.data.widgetJson.config.filters || [],
        filterFormJson
      )
      //  remove selectedParentList;  remove selectedChartInfo
      this.removeSelectedConfig(gridItem.widgetId)
      this.addHideList(gridItem.widgetId)
    }

    const chartType = Array.isArray(gridItem.chartType)
      ? gridItem.chartType[1]
      : gridItem.chartType

    this.$set(gridItem, 'isLoading', true)
    this.errorFilterWidgets = this.errorFilterWidgets.filter(
      (item) => item !== gridItem.i
    )
    getWidgetData(query)
      .then((response) => {
        if (response.data.code === 1106) {
          this.errorFilterWidgets.push(gridItem.i)
          this.errorMsgMap[gridItem.i] = response.data.message
        }
        if ([100, 1106].includes(response.data.code)) {
          // 处理数据
          let dataResult =
            parseGetDataResult(
              response.data?.result || { columns: [], data: [], extraData: {} },
              chartType,
              gridItem
            ) || []
          if (query.data.widgetJson !== undefined) {
            dataResult = restructGetDataResult(
              dataResult,
              query.data.widgetJson
            )
          }
          this.gridItemSyncBindData({
            data: {
              query,
              data: dataResult,
              columns: gridItem.columns,
              formData: gridItem.chartOptions,
            },
            gridItem,
            index,
          })
          //  remove target widget from hideList
          const targetIndex = this.hideWidgetList.indexOf(gridItem.widgetId)
          if (targetIndex >= 0) {
            this.hideWidgetList.splice(targetIndex, 1)
          }
        }
        if (withFilter) {
          this.releaseLoadingTag(gridItem.widgetId)
        }
        this.$set(gridItem, 'isLoading', false)
      })
      .catch(() => {
        this.$set(gridItem, 'isLoading', false)
        if (withFilter) {
          this.releaseLoadingTag(gridItem.widgetId)
        }
      })
  }

  /** 释放加载标识 */
  releaseLoadingTag(widgetId: string | number) {
    const key = `widget_${this.filterFormExecuteId}`
    this.$set(
      this.loadingMaps,
      key,
      this.loadingMaps[`widget_${this.filterFormExecuteId}`].filter(
        (id: any) => id !== widgetId
      )
    )
    setTimeout(() => {
      if (this.loadingMaps[key].length === 0) {
        this.isFilterFormTaskRunning = false
      }
    }, 100)
  }

  onChartInstanceInit(chartInstance: any, item: GridItemDataInterface) {
    this.chartInstanceMaps[item.i] = chartInstance
  }

  private get selecetedChartInfo() {
    return VisualizationStore.selectedChartInfo
  }

  @Watch('selecetedChartInfo', { deep: true })
  private handleSelectedInfoChange(newValue: any) {
    if (newValue.length > 0) {
      this.checkIfEnableInteract()
    }
  }

  //  判断哪些被控制的chart, 是否满足条件getData
  private checkIfEnableInteract() {
    //  之前添加过的不用重复添加；！！！！！！
    const selectedWidgetLength = this.selectedParentList.length
    const latestSelectWidgetId = this.selecetedChartInfo[
      this.selecetedChartInfo.length - 1
    ].widgetId

    this.interactRelateList.forEach((item: any) => {
      /** 把筛选器组件先给屏蔽了 */
      if (checkIsFilterForm(item)) {
        return
      }
      if (checkArrayIncludes(this.selectedParentList, item.parents)) {
        // 包含关系
        const index: any = this.gridList.findIndex(
          (instance: any) => instance.widgetId === item.child
        )
        this.resetFiltersForItem(index, item.child)
        if (
          item.parents.length <= selectedWidgetLength &&
          item.child !== latestSelectWidgetId &&
          item.parents.includes(latestSelectWidgetId)
        ) {
          //  点击自己，不会重复渲染； 点击无关的chart， 已渲染的不要重复渲染
          // 达到渲染条件
          const gridItem = this.gridList[index]
          // console.log('!!!!!!!!', gridItem)
          if (gridItem.chartType === 'table') {
            this.getTableData(gridItem, [])
          } else {
            this.getWidgetData(gridItem.chartOptions, gridItem, index) //  widgetJson - config - filters 需要处理
          }
        }
      }
    })
  }

  //  设置相应的filter 为gridItem 【一对一控制； 多对一控制】【不考虑多级filter，只考虑一级】
  private resetFiltersForItem(index: number, targetId: number) {
    //  selecetedChartInfo 新增在末尾
    const targetItem = this.interactRelateList.find(
      (item) => item.child === targetId
    )
    if (checkIsFilterForm(targetItem)) {
      return
    }

    const target = this.gridList[index]
    //  先置空，在对重新添加filter
    if (target.chartType === 'table' && target.chartOptions) {
      //  table 的情况
      this.gridList[index].chartOptions.filters = []
    } else if (this.gridList[index].dataQuery.data.widgetJson.config) {
      //  非table 的情况
      this.gridList[index].dataQuery.data.widgetJson.config.filters = []
    }

    this.selecetedChartInfo.forEach((item: any) => {
      if (targetItem.parents.includes(item.widgetId)) {
        // 只处理和自己相关的filter && 在移除filter 的时候，同样要移除 - child widget 要改变；
        if (target.chartType === 'table' && target.chartOptions) {
          //  table 的情况
          this.gridList[index].chartOptions.filters.push({
            col: item.xAttrStr,
            filterType: '=',
            values: [item.xAttrValue],
            desc: item.desc,
          })
        } else if (this.gridList[index].dataQuery.data.widgetJson.config) {
          //  非table 的情况
          this.gridList[index].dataQuery.data.widgetJson.config.filters.push({
            col: item.xAttrStr,
            filterType: '=',
            values: [item.xAttrValue],
            desc: item.desc,
          })
        }
      }
    })
  }

  //  CHECK 该图表区域是否被选中过，若已经选中 则返回true, 反之则false；
  private checkIfChartSelected(item: any) {
    if (this.selecetedChartInfo.length === 0) {
      return false
    }
    const result = this.selecetedChartInfo.find((info: any) => {
      return item.widgetId === info.widgetId
    })
    if (result?.widgetId) {
      return (
        item.xAttrValue === result.xAttrValue &&
        item.xAttrStr === result.xAttrStr
      )
    }
    return false
  }

  public onChartEvent(data: any, item: any /* ,index:number */) {
    const isGraph = this.checkIsGraph(item)

    console.log('data.name', data.name)
    switch (data.name) {
      case 'selectedChange':
      case 'graphNodeClick':
        this.currentFilterFormItemAsCharts = []
        this.currentFilterIndex = ''
        console.log(data.event, item, '^^^^^')
        //   check if item.widgetId is in
        if (this.parentList.includes(item.widgetId)) {
          const result = {
            xAttrStr: !isGraph ? item.chartOptions.labelKey : data.value.key,
            xAttrValue: !isGraph
              ? data.event.data.data[item.chartOptions.labelKey]
              : data.value.value,
            // ...data.event.data.data,
            widgetId: item.widgetId,
            desc: !isGraph
              ? item.dataQuery.data.widgetJson.config.keys[0].desc
              : '',
          }
          if (isGraph && item.chartOptions.value.nodes.length > 0) {
            const targetValue = item.chartOptions.value.nodes[0].attrs.filter(
              (att: any) => att.key === data.value.key
            )[0].value
            result.desc = typeof targetValue === 'string' ? 'varchar' : 'int' // graph中的数据属性只会有字符串和数字两种类型
          }

          if (this.checkIfChartSelected(result)) {
            this.removeSelectedConfig(item.widgetId)
            this.addHideList(item.widgetId)
          } else {
            this.removeSelectedConfig(item.widgetId)
            this.addHideList(item.widgetId)
            //  同一个widget 只保留一个filter info
            VisualizationStore.addSelectedChartInfo(result)

            this.selectedParentList = [
              ...new Set([...this.selectedParentList, item.widgetId]),
            ]
          }
        }

        // console.error(
        //   'chart event',
        //   data,
        //   'data.event.data.data',
        //   data.event.data.data
        // )
        break
      default:
        //  console.error('chart event', data)
        break
    }
  }

  //  将目标widgetId 下关联的图表加入到hideList 中
  private addHideList(widgetId: Number) {
    //  add back hideWidgetList    reverstInteractList
    const targetChildren = this.reverstInteractList.find((x: any) => {
      return x.parent === widgetId
    })
    this.hideWidgetList = [
      ...new Set([...this.hideWidgetList, ...(targetChildren?.children || [])]),
    ]
  }

  /** 获取tooltip渲染文字 */
  public getGraphTooltipInfo(chart: any, setting: any): Array<string> {
    console.log('chart', chart, 'setting', setting)
    if (setting.tooltipType === 1 && setting.nodeTooltipInfo) {
      return isArray(setting.nodeTooltipInfo.attrs) &&
        setting.nodeTooltipInfo.attrs.length > 0
        ? setting.nodeTooltipInfo.attrs.reduce(
            (rec: Array<string>, item: any) => {
              rec.push(`${item.key}：${item.value}`)
              return rec
            },
            [] as Array<string>
          )
        : ['-- 空 -- ']
    }
    if (setting.tooltipType === 2 && setting.linkTooltipInfo) {
      return isArray(setting.linkTooltipInfo.attrs) &&
        setting.linkTooltipInfo.attrs.length > 0
        ? setting.linkTooltipInfo.attrs.reduce(
            (rec: Array<string>, item: any) => {
              rec.push(`${item.key}：${item.value}`)
              return rec
            },
            [] as Array<string>
          )
        : ['-- 空 -- ']
    }
    return ['-- 空 --']
  }

  /** 检测filter-form是否可执行或重置 */
  checkFilterFormIsEnabled(gridItem: GridItemDataInterface) {
    return checkFilterFormIsEnabled(gridItem)
  }

  /** 是否是筛选器控件 */
  checkIsFilterForm(gridItem: gridItemFilterInterface) {
    return checkIsFilterForm(gridItem)
  }

  /** 获取组件类型 */
  getChartType(gridItem: GridItemDataInterface) {
    if (checkIsFilterForm(gridItem)) {
      return filterFormTypeName
    }
    return Array.isArray(gridItem.chartType)
      ? gridItem.chartType[1]
      : gridItem.chartType
  }

  /** 重新请求数据 */
  reloadWidget(gridItem: GridItemDataInterface, index: number) {
    this.getWidgetData(gridItem.chartOptions, gridItem, index, true)
  }

  /** 获取gridIndex */
  getGridIndex(index: string) {
    return this.gridList.findIndex((item: any) => item.i === index)
  }

  hoverFilterFormInfo: HoverFilterFormInfo | null = null

  /** 筛选控件中的筛选条件改变 */
  filterChange(data: any, gridItem: GridItemDataInterface) {
    console.log('filterChange done', data, gridItem)
  }

  /** 当前高亮filterForm 相关信息 */
  currentFilterFormItemAsCharts: any[] = []

  currentFilterIndex: string = ''

  /** filter-form 点击 */
  filterFormClicked(item: GridItemDataInterface) {
    this.currentFilterFormItemAsCharts =
      item.chartOptions?.associationChart || []
    this.currentFilterIndex = item.i as string
  }

  /** 获取关联图表 */
  get filterAssociationChart() {
    let result = [] as Array<string | number>
    if (this.hoverFilterFormInfo) {
      const currentGrid = this.gridList.find(
        (item) => item.i === this.hoverFilterFormInfo?.i
      )
      result = currentGrid?.chartOptions?.associationChart || []
    }
    return result
  }

  /** 鼠标移入filter-form */
  handleFilterFormMouseEnter(gridItem: GridItemDataInterface) {
    this.hoverFilterFormInfo = {
      i: gridItem.i as any,
      widgetId: gridItem.widgetId,
    }
  }

  /** 鼠标移出fitler-form */
  handleFilterFormMouseLeave(gridItem: GridItemDataInterface) {
    if (this.hoverFilterFormInfo?.i === gridItem.i) {
      this.hoverFilterFormInfo = null
    }
  }

  /** 筛选器运行状态检测对象 */
  loadingMaps: any = {}

  /** 判断筛选执行情况 */
  checkIsRunning(gridItem: GridItemDataInterface) {
    return (
      this.loadingMaps[`widget_${gridItem.widgetId}`] &&
      this.loadingMaps[`widget_${gridItem.widgetId}`].length > 0
    )
  }

  /** 加载关联图表数据 */
  loadAssociationCharts(gridItem: GridItemDataInterface) {
    const associationCharts = ((gridItem.chartOptions?.associationChart ||
      []) as Array<string | number>).filter((id) => {
      return this.gridList.findIndex((grid) => grid.widgetId === id) > -1
    })
    if (associationCharts.length === 0) {
      message.info('筛选器没有有效关联组件')
      this.filterFormExecuteId = ''
      return
    }
    this.filterFormExecuteId = gridItem.widgetId
    this.isFilterFormTaskRunning = true
    this.$set(
      this.loadingMaps,
      `widget_${gridItem.widgetId}`,
      cloneDeep(associationCharts)
    )
    const filterFormWidgetJSON = getFilterDataFromFilterForm(gridItem)
    associationCharts.forEach((wegetId) => {
      const asGridItem = this.gridList.find((item) => {
        return item.widgetId === wegetId
      })
      if (asGridItem) {
        this.$set(asGridItem, 'filterFormJson', filterFormWidgetJSON)
        this.getWidgetData(
          asGridItem.chartOptions,
          asGridItem,
          this.getGridIndex(asGridItem.i),
          true
        )
      }
    })
  }

  /** 重置filter-form */
  filterFormReset(gridItem: GridItemDataInterface) {
    const index = this.getGridIndex(gridItem.i as any)
    let queryData = {} as any
    const { chartOptions } = gridItem
    const form = this.$refs[`filter-form-${gridItem.i}`] || []

    if (form && (form as any)[0]) {
      queryData = (form as any)[0].getResetData() || {}
    }
    const gridItemSet = {
      ...gridItem,
      chartOptions: {
        ...chartOptions,
        queryData,
      },
    }

    this.$set(this.gridList, index, gridItemSet)
    this.$nextTick(() => {
      this.loadAssociationCharts(gridItemSet)
    })
  }

  /** 执行或者停止筛选 */
  filterFormExecute(isStart: boolean, gridItem: GridItemDataInterface) {
    if (isStart) {
      this.loadAssociationCharts(gridItem)
    } else {
      this.filterFormExecuteId = ''
    }
  }

  /**
   * 获取graph 数据
   */
  private async getGraphData(
    gridItem: GridItemDataInterface,
    withFilter?: boolean
  ) {
    this.$set(gridItem, 'isLoading', true)
    const callback = withFilter
      ? (data: any) => filterGraphData(gridItem.filterFormJson || [], data)
      : undefined
    const value = await getWidgetGraphData(gridItem, this.projectId, callback)
    if (gridItem.dataQuery?.data.widgetJson.config.filters.length > 0) {
      this.filterGraphData(
        value,
        gridItem.dataQuery?.data.widgetJson.config.filters
      )
    }
    const { nodePosition = [] } = gridItem.chartOptions // 处理position位置
    value.nodes.forEach((node: any) => {
      const targetPosition = (nodePosition as Array<{
        id: string
        x: number
        y: number
      }>).filter((p) => p.id === node.id)[0]
      if (targetPosition) {
        node.x = targetPosition.x
        node.y = targetPosition.y
      }
    })

    this.$set(gridItem.chartOptions, 'value', value)
    this.$set(gridItem, 'showChart', true)
    this.$set(gridItem, 'isLoading', false)

    const targetIndex = this.hideWidgetList.indexOf(gridItem.widgetId)
    if (targetIndex >= 0) {
      this.hideWidgetList.splice(targetIndex, 1)
    }

    if (withFilter) {
      setTimeout(() => {
        this.releaseLoadingTag(gridItem.widgetId)
      }, 100)
    }
  }

  /**
   * 过滤graphData
   */
  private filterGraphData(
    value: any,
    filters: { col: string; values: any[] }[]
  ) {
    value.nodes = value.nodes.filter((node: any) => {
      // eslint-disable-next-line unicorn/prevent-abbreviations
      const parsedAttrs = node.attrs.reduce((accu: any, current: any) => {
        accu[current.key] = current.value
        return accu
      }, {})
      return filters.every((filter) =>
        filter.values.includes(parsedAttrs[filter.col])
      )
    })
  }

  get pipelineId() {
    return VisualizationStore.pipelineId
  }

  get dataset(): any[] {
    return VisualizationStore.dataset
  }
}
</script>

<style lang="less" scoped>
.preview {
  background-color: #ebeaef;
  height: 100vh;
  overflow: hidden;

  .content-box {
    height: calc(100vh - 64px);
    overflow-y: auto;
    width: 100%;
  }

  .grid-item {
    background-color: #fff;
    border: 1px solid #d9d9d980;
    border-radius: 4px;
    overflow: hidden;

    &.active,
    &:hover {
      border: 1px solid #c5c9ff;

      /deep/ .comp-filter-form-toolbar {
        opacity: 1;
        pointer-events: all;
      }
    }
  }

  .grid-item.hover-border {
    border: 1px solid rgb(197, 201, 255);
  }

  .grid-item.select-border,
  .grid-item.active-border {
    border: 1px solid rgb(105, 115, 255);
  }

  .container {
    background-color: #fff;
    height: calc(100vh - 64px);
    margin: 0 auto;
  }

  .loading-mask,
  .no-data {
    align-content: center;
    align-items: center;
    display: flex;
    height: 100%;
    justify-content: center;
    text-align: center;
    width: 100%;
    z-index: 100;

    .loading-status-img,
    .no-data-img {
      display: block;
      height: auto;
      max-width: 75%;
      width: 208px;
    }
  }

  .text-editor {
    height: calc(100% - 60px);
    padding: 0 15px;
  }

  .height-reset {
    height: calc(100% - 18px);
  }
}

.interact-mask {
  background: #fff !important;
  height: calc(100% - 38px);
  margin-top: 38px;
  position: absolute;
  width: 100%;
  z-index: 100;

  &.no-title {
    height: 100%;
    margin-top: 0;
  }
}

.loading-mask {
  background: rgba(0, 0, 0, 0.05) !important;
  position: absolute;
  z-index: 1;

  .loading-status {
    align-content: center;
    align-items: center;
    color: #222432;
    display: flex;
    flex-flow: row nowrap;
    font-size: 12px;
    font-weight: 500;
    left: 50%;
    letter-spacing: 1px;
    line-height: 36px;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    vertical-align: middle;

    .loading-text {
      display: inline-block;
      padding-left: 4px;
    }

    .sync-icon {
      color: #5760e6;
      font-size: 24px;
    }
  }
}

.no-data {
  background: transparent;

  .no-data-block {
    align-items: center;
    display: flex;
    flex-flow: column;
    justify-content: center;
  }

  .no-data-text {
    color: #d9d9d9;
  }
}

.title-box {
  color: #5d637e;
  font-size: 14px;
  font-weight: 500;
  height: 38px;
  letter-spacing: 1px;
  line-height: 38px;
  overflow: hidden;
  padding: 0 16px;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;

  /deep/ .comp-filter-form-toolbar {
    position: absolute;
    right: 16px;
    top: 0;
    z-index: 10;
  }

  &.no-title {
    display: none;
    height: 24px;
  }
}

/deep/ .chart-title {
  color: #5d637e !important;
  font-size: 14px !important;
  font-weight: 500;
  height: 38px;
  line-height: 38px;
  padding: 0 16px;
  text-align: left !important;
}

.control-box-list {
  padding-bottom: 0;
  padding-top: 14px;

  &.list-vertical {
    padding-bottom: 2px;
  }
}

.graph-tooltip {
  background: #fff;
  border-radius: 2;
  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.15);
  max-width: 200px;
  min-width: 80px;
  padding: 12px;
  pointer-events: none;
  position: absolute;
  z-index: 100000;

  p {
    color: #555;
    font-size: 12px;
    line-height: 16px;
    margin: 0;
    overflow: hidden;
    padding: 0;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.filter-form-hander {
  background: transparent;
  box-sizing: border-box;
  height: 100%;
  left: 0;
  padding-top: 24px;
  position: absolute;
  top: 0;
  width: 100%;
  z-index: 8;
}

.preview .grid-item.remove-border {
  border: 0;
}

.remove-title {
  display: none;
}
</style>
